﻿#if MT1
using System;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Threading;
using System.Linq;

using RAD.LightOne.Cache;
using RAD.LightOne;
using System.Drawing;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using RAD.LightOne.Data;

namespace RAD.LightOne
{
    #region Documentation
    /// <summary>
    /// Classe que encapsula um objeto traduzido a uma versão intermediária, na verdade o pedaço do banco que esteja sendo utilizado
    /// em alguma operação de persistência e, assim como um banco de dados, esta classe é em si uma
    /// coleção de tabelas, representadas aqui por InternalObj que, assim como a classe que foi traduzida, contém dados de uma ou mais 
    /// tabelas.
    /// </summary>
    #endregion Documentation
    internal sealed class InternalObj : ICollection<Table>, ICloneable
    {
        #region Inner Types

        #region Documentation
        ///<summary>
        /// Classe privada auxiliar na execução de operações multi-thread evitando a concorrência na execução de comandos ao banco de dados ou
        /// interferência de várias threads nos valores a ser definidos.
        ///</summary>
        #endregion
        private sealed class FieldInstanceSetter
        {
            private IDbTransaction trx;
            private MyObject instance;
            private FieldInfo field;
            private Guid id;

            public FieldInstanceSetter(IDbTransaction trx, MyObject instance, FieldInfo field, Guid id)
            {
                this.trx = trx;
                this.instance = instance;
                this.field = field;
                this.id = id;
            }

            private void SetInstanceToInstance(IDbTransaction trx, MyObject instance, FieldInfo field, Guid id)
            {
                try
                {
                    Finder finder = new Finder();
                    MyObject found;
                    if (field.FieldType.IsInterface)
                    {
                        
                        found = finder.FindById(typeof(MyObject), id, trx);
                    }
                    else
                    {
                        found = finder.FindById(field.FieldType, id, trx);
                    }
                    field.SetValue(instance, found);
                }
                catch
                {
                    field.SetValue(instance, null);
                }
            }

            #region Documentation
            ///<summary>
            /// Seta, define, os valores passados segundo os parâmetros de inicialização, pode iniciar novo ciclo de carregamentos.
            ///</summary>
            #endregion
            public void Flush()
            {
                this.SetInstanceToInstance(this.trx, this.instance, this.field, this.id);
            }
        }

        private sealed class PropertyInstanceSetter
        {
            private IDbTransaction trx;
            private MyObject instance;
            private PropertyInfo property;
            private Guid id;

            public PropertyInstanceSetter(IDbTransaction trx, MyObject instance, PropertyInfo property, Guid id)
            {
                this.trx = trx;
                this.instance = instance;
                this.property = property;
                this.id = id;
            }

            private void SetInstanceToInstance(IDbTransaction trx, MyObject instance, PropertyInfo property, Guid id)
            {
                try
                {
                    Finder finder = new Finder();
                    MyObject found;
                    if (property.PropertyType.IsInterface)
                    {

                        found = finder.FindById(typeof(MyObject), id, trx);
                    }
                    else
                    {
                        found = finder.FindById(property.PropertyType, id, trx);
                    }
                    property.SetValue(instance, found, null);
                }
                catch
                {
                    property.SetValue(instance, null,null);
                }
            }

            #region Documentation
            ///<summary>
            /// Seta, define, os valores passados segundo os parâmetros de inicialização, pode iniciar novo ciclo de carregamentos.
            ///</summary>
            #endregion
            public void Flush()
            {
                this.SetInstanceToInstance(this.trx, this.instance, this.property, this.id);
            }
        }
        #endregion

        #region Fields

        private List<Table> indexedTables;

        private Dictionary<string,Table> namedTables;

        private Type type;

        //private SetInstanceToInstanceArgs argument;

        #endregion Fields

        #region Properties

        #region Documentation
        /// <summary>
        /// Retorna o tipo traduzido por este objeto intermediário.
        /// </summary>
        #endregion
        public Type Type
        {
            get
            {
                return this.type;
            }
        }

        #region Documentation
        /// <summary>
        /// Retorna o FullName da classe traduzida pelo onjeto intermediário.
        /// </summary>
        #endregion
        public string FullClassName
        {
            get
            {
                return this.type.FullName;
            }
        }

        #region Documentation
        /// <summary>
        /// Retorna um objeto de tabela eplo seu índice.
        /// </summary>
        /// <param name="index">índice, posição do objeto.</param>
        #endregion
        public Table this[int index]
        {
            get
            {
                return this.indexedTables[index];
            }
        }

        #region Documentation
        /// <summary>
        /// Retorna um ítem de table por seu nome, ou seja o nome da tabela por ele mapeada.
        /// </summary>
        /// <param name="name">nome do item/tabela.</param>
        #endregion
        public Table this[string key]
        {
            get
            {
                return this.namedTables[key];
            }
        }

        #endregion Properties

        #region Constructors

        #region Documentation
        /// <summary>
        /// Construtor onde se passa o tipo que a instânca deve traduzir.
        /// </summary>
        /// <param name="type">tipo a ser traduzido.</param>
        #endregion
        public InternalObj(Type type)
        {
            if (type == null) throw new ArgumentNullException("type");

            this.type = type;
            this.indexedTables = new List<Table>();
            this.namedTables = new Dictionary<string, Table>();
        }

        #endregion Constructors

        #region Methods

        #region Documentation
        /// <summary>
        /// Método que seta os valores contidos no objeto intermediário para a instância real da classe que ele mapeia.
        /// </summary>
        /// <param name="instance">instância que receberá os valores</param>
        /// <param name="trx">transação utilizada na recursividade de finds caso necessário.</param>
        #endregion Documentation
        public void SetValuesToInstance(MyObject instance, IDbTransaction trx)
        {
            if (!(trx is MyDbTransaction)) throw new InvalidCastException("trx is not be a MyBdTransaction.");
            ConcurrentBag<Task> tasks = new ConcurrentBag<Task>();

            DirectFieldMapper maped = new DirectFieldMapper(this);
            List<Type> types = new List<Type>();
            types.AddRange(MetadataReader.GetHierarchy(instance.GetType(), typeof(MyObject)));

            #region foreach
            foreach (Type t in types)
            {
                IEnumerable<FieldInfo> fieldInfos = null;
                IEnumerable<PropertyInfo> propInfos = null;
                #region Documentation
                ///<summary>
                /// Estas tasks podem correr em paralelo mas são interdependentes para o contínuo do fluxo de operações, por isso são construidas como parâmetros
                /// do método estático WaitAll da classe Task, que esperará que ambos terminem para devolver o fluxo normal de execução ao método.
                ///</summary>
                #endregion
                Task.WaitAll(
                    Task.Factory.StartNew(() =>
                    {
                        fieldInfos = t.GetFields(Global.BINDING_FLAGS).Where(s => s.Name == "lastPersistence" || DBFieldAttribute.IsDefined(s));
                    })
                    ,
                    Task.Factory.StartNew(() =>
                    {
                        propInfos = t.GetProperties(Global.BINDING_FLAGS).Where(s => DBFieldAttribute.IsDefined(s));
                    })
                );
                
                
                #region foreachs
                /*
                 * com o advento do Linq algumas estruturas puderam ser separadas, simplificadas e tratadas de forma concorrente.
                 */

                //primeiro laço, estruturas de dados primitivos
                if (fieldInfos.Count() > 0)
                {
                    tasks.Add(Task.Factory.StartNew(() =>
                    {
                        foreach (FieldInfo field in fieldInfos.Where(s => !MyObject.IsMyObject(s.FieldType)).Where(s => s.Name == "lastPersistence" || (((DBFieldAttribute)s.GetCustomAttributes(typeof(DBFieldAttribute), false)[0]).DataOperations & DataOperations.Select) == DataOperations.Select))
                        {
                            DBFieldAttribute attrib;
                            if (field.Name == "lastPersistence")
                            {
                                attrib = new DBFieldAttribute(LastPersistenceAttribute.GetDefinedAttribute(instance.GetType()).FieldName);
                            }
                            else
                            {
                                attrib = (DBFieldAttribute)field.GetCustomAttributes(typeof(DBFieldAttribute), false)[0];
                            }

                            
                            
                            #region Bloco de instruções
                            try
                            {
                                if (maped[attrib.FieldName].Value is DBNull)
                                {
                                    field.SetValue(instance, null);
                                }
                                else
                                {
                                    if (Lazy.IsILazy(field.FieldType))
                                    {

                                        try
                                        {
                                            ConstructorInfo ctorInfo = field.FieldType.GetConstructor(Global.BINDING_FLAGS, null, new Type[] { typeof(Guid) }, null);
                                            object found = ctorInfo.Invoke(new object[] { new Guid(maped[attrib.FieldName].Value.ToString()) });
                                            field.SetValue(instance, found);
                                        }
                                        catch
                                        {
                                            field.SetValue(instance, null);
                                        }
                                    }
                                    else
                                    {
                                        #region Comments
                                        /*
                                         * Tratamento para enum nullable
                                         */
                                        #endregion
                                        bool isNullableEnum;
                                        try
                                        {
                                            isNullableEnum = field.FieldType.IsGenericType &&
                                                                    field.FieldType.GetGenericTypeDefinition() == typeof(Nullable<>) &&
                                                                    field.FieldType.GetGenericArguments()[0].IsSubclassOf(typeof(Enum));
                                        }
                                        catch { isNullableEnum = false; }
                                        if (isNullableEnum)
                                        {
                                            Type nullableType = field.FieldType.GetGenericArguments()[0];

                                            object value = Enum.ToObject(nullableType, maped[attrib.FieldName].Value);

                                            field.SetValue(instance, Activator.CreateInstance(field.FieldType, new object[] { value }));
                                        }
                                        else
                                        {
                                            object value;
                                            Field mapped = maped[attrib.FieldName];
                                            if (field.FieldType.IsEnum)
                                            {
                                                value = Enum.ToObject(field.FieldType, mapped.Value);
                                            }
                                            else if (field.FieldType == typeof(Image) || field.FieldType.IsSubclassOf(typeof(Image)))
                                            {
                                                MemoryStream mem = new MemoryStream((byte[])mapped.Value);
                                                value = Image.FromStream(mem);
                                            }
                                            else
                                            {
                                                value = Convert.ChangeType(mapped.Value, field.FieldType);
                                            }
                                            field.SetValue(instance, value);
                                        }
                                    }
                                }
                            }
                            catch (Exception except)
                            {
                                throw new ArgumentException("Erro occours on " + instance.GetType().Name + "." + field.Name + " when set a value, " + except.Message, except.InnerException);
                            }
                            #endregion
                        }
                    }));

                    //segundo laço, classes
                    tasks.Add(Task.Factory.StartNew(() =>
                   {
                       foreach (FieldInfo field in fieldInfos.Where(s => MyObject.IsMyObject(s.FieldType)).Where(s => (((DBFieldAttribute)s.GetCustomAttributes(typeof(DBFieldAttribute), false)[0]).DataOperations & DataOperations.Select) == DataOperations.Select))
                       {
                           DBFieldAttribute attrib = (DBFieldAttribute)field.GetCustomAttributes(typeof(DBFieldAttribute), false)[0];
                           #region Bloco de instruções
                           if (string.IsNullOrEmpty(maped[attrib.FieldName].Value.ToString()))
                           {
                               field.SetValue(instance, null);
                           }
                           else
                           {
                               FieldInstanceSetter setter = new FieldInstanceSetter(trx, instance, field, new Guid(maped[attrib.FieldName].Value.ToString()));
                               setter.Flush();
                           }

                           #endregion Bloco de instruções
                       }
                   }
                   ));
                }

                if (propInfos.Count() > 0)
                {
                    tasks.Add(Task.Factory.StartNew(() =>
                    {
                        foreach (PropertyInfo property in propInfos.Where(s => !MyObject.IsMyObject(s.PropertyType)).Where(s => (((DBFieldAttribute)s.GetCustomAttributes(typeof(DBFieldAttribute), false)[0]).DataOperations & DataOperations.Select) == DataOperations.Select))
                        {
                            DBFieldAttribute attrib = (DBFieldAttribute)property.GetCustomAttributes(typeof(DBFieldAttribute), false)[0];
                            #region Bloco de instruções
                            try
                            {
                                if (maped[attrib.FieldName].Value is DBNull)
                                {
                                    property.SetValue(instance, null, null);
                                }
                                else
                                {
                                    if (Lazy.IsILazy(property.PropertyType))
                                    {

                                        try
                                        {
                                            ConstructorInfo ctorInfo = property.PropertyType.GetConstructor(Global.BINDING_FLAGS, null, new Type[] { typeof(Guid) }, null);
                                            object found = ctorInfo.Invoke(new object[] { new Guid(maped[attrib.FieldName].Value.ToString()) });
                                            property.SetValue(instance, found, null);
                                        }
                                        catch
                                        {
                                            property.SetValue(instance, null, null);
                                        }
                                    }
                                    else
                                    {
                                        #region Comments
                                        /*
                                         * Tratamento para enum nullable
                                         */
                                        #endregion
                                        bool isNullableEnum;
                                        try
                                        {
                                            isNullableEnum = property.PropertyType.IsGenericType &&
                                                                    property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) &&
                                                                    property.PropertyType.GetGenericArguments()[0].IsSubclassOf(typeof(Enum));
                                        }
                                        catch { isNullableEnum = false; }
                                        if (isNullableEnum)
                                        {
                                            Type nullableType = property.PropertyType.GetGenericArguments()[0];

                                            object value = Enum.ToObject(nullableType, maped[attrib.FieldName].Value);

                                            property.SetValue(instance, Activator.CreateInstance(property.PropertyType, new object[] { value }),null);
                                        }
                                        else
                                        {
                                            object value;
                                            Field mapped = maped[attrib.FieldName];
                                            if (property.PropertyType.IsEnum)
                                            {
                                                value = Enum.ToObject(property.PropertyType, mapped.Value);
                                            }
                                            else if (property.PropertyType == typeof(Image) || property.PropertyType.IsSubclassOf(typeof(Image)))
                                            {
                                                MemoryStream mem = new MemoryStream((byte[])mapped.Value);
                                                value = Image.FromStream(mem);
                                            }
                                            else if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                                            {
                                                Type under = Nullable.GetUnderlyingType(property.PropertyType);
                                                value = Convert.ChangeType(mapped.Value, under);
                                            }
                                            else
                                            {

                                                value = Convert.ChangeType(mapped.Value, property.PropertyType);
                                            }
                                            property.SetValue(instance, value, null);
                                        }
                                    }
                                }
                            }
                            catch (Exception except)
                            {
                                throw new ArgumentException("Erro occours on " + instance.GetType().Name + "." + property.Name + " when set a value, " + except.Message, except.InnerException);
                            }
                            #endregion
                        }
                    }));

                    //segundo laço, classes
                    tasks.Add(Task.Factory.StartNew(() =>
                    {
                        foreach (PropertyInfo property in propInfos.Where(s => MyObject.IsMyObject(s.PropertyType)).Where(s => (((DBFieldAttribute)s.GetCustomAttributes(typeof(DBFieldAttribute), false)[0]).DataOperations & DataOperations.Select) == DataOperations.Select))
                        {
                            DBFieldAttribute attrib = (DBFieldAttribute)property.GetCustomAttributes(typeof(DBFieldAttribute), false)[0];
                            #region Bloco de instruções
                            if (string.IsNullOrEmpty(maped[attrib.FieldName].Value.ToString()))
                            {
                                property.SetValue(instance, null, null);
                            }
                            else
                            {
                                PropertyInstanceSetter setter = new PropertyInstanceSetter(trx, instance, property, new Guid(maped[attrib.FieldName].Value.ToString()));
                                setter.Flush();
                            }

                            #endregion Bloco de instruções
                        }
                    }
                   ));
                }
                #endregion
            }
            #endregion

            try
            {
                if (tasks.Count > 0)
                {
                    Task.WaitAll(tasks.ToArray());
                }
            }
            catch { }
            
        }

        #region Documentation
        /// <summary>
        /// Método que suprime a existência de campos chave repetidos nas tabelas dependentes, id, className and lastPersistence, serão removidos das tabelas de indice > 0.
        /// </summary>
        #endregion
        public void SuppressKeyOnDependantTables()
        {
            if (this.Count > 1)
            {
                for (int i = 1; i < this.Count; i++)
                {
                    var remove = (from f in this[i] where f.NameOnObject == "id" || f.NameOnObject == "className" || f.NameOnObject == "lastPersistence" select f).ToArray();
                    foreach (var r in remove)
                    {
                        this[i].Remove(r);
                    }
                }
            }
        }
        #endregion Methods

        #region ICollection<DBTableItem> Members

        public void Add(Table item)
        {
            lock (this.indexedTables)
            {
                this.indexedTables.Add(item);
                this.namedTables.Add(item.Name, item);
                item.Index = this.indexedTables.IndexOf(item);
            }
        }

        public void Clear()
        {
            lock (this.indexedTables)
            {
                this.indexedTables.Clear();
                this.namedTables.Clear();
            }
        }

        public bool Contains(Table item)
        {
            return this.indexedTables.Contains(item);
        }

        public void CopyTo(Table[] array, int arrayIndex)
        {
           this.indexedTables.CopyTo(array,arrayIndex);
        }

        public void CopyTo(Table[] array)
        {
            this.indexedTables.CopyTo(array);
        }

        public int Count
        {
            get { return this.indexedTables.Count; }
        }

        public bool IsReadOnly
        {
            get { return true; }
        }

        public bool Remove(Table item)
        {
            lock (this.indexedTables)
            {
                this.namedTables.Remove(item.Name);
                return this.indexedTables.Remove(item);
            }
        }

        #endregion

        #region IEnumerable<DBTableItem> Members

        public IEnumerator<Table> GetEnumerator()
        {
            return this.indexedTables.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.namedTables.GetEnumerator();
        }

        #endregion

        #region ICloneable Members

        public object Clone()
        {
            object newObj = this.MemberwiseClone() as object;
            return newObj;
        }

        #endregion
    }
}
#endif